Estudiando el firmware de impresión 3D más famoso; Marlin ;  se puede aprender muchísimo acerca de la programación en C++ y cómo se pueden usar algunos aspectos escondidos muy interesantes para extender nuestros programas en Arduino.

Vamos a hablar de los Arduino MACROS, que es lo mismo que una función, pero el genérico para otra serie de programas se utiliza este término. Por ejemplo para automatizar taréas dentro de un programa como Excel en el que tienes que alterar muchos documentos de una misma manera; se puede crear una MACRO con instrucciones sencillas del programa sin necesidad de extenderse en conceptos de programación. Por ejemplo, yo lo utilizo muchísimo con Photoshop para redimensionar carpetas enteras de imágenes. En el ejemplo de Photoshop , las MACROS se llaman “Acciones”.

Es realmente útil en el ámbito de una interfaz o programa ya diseñado, pero en el ámbito de la programación quizás el concepto es algo difuso, ya que programamos nuestras funciones de la manera habitual; ¿para qué necesitamos las MACROS?

Con las MACROS, se define el nombre que se asocia a una variable o función y se reemplazan todas las ocurrencias que existen con ese nombre identificador.

El preprocesador sustituirá cada ocurrencia del identificador_de_macro en el fichero fuente, por la secuencia definida.

La idea es que a la hora de compilar el programa, pueden existir configuraciones distintas para distintos dispositivos y que pueden ser innecesarias por ocupar mucho espacio. Por ello, se permite desarrollar estas MACROS que solo integrarían las sentencias definidas ocupando la memoria que se va a utilizar.

Mostraremos varios ejemplos para estudiar este proceso poco a poco

  • DEFINE de una variable
  • DEFINE de una función
  • Operador ## Nombres de variables siguiendo un patrón. Las variables no han sido creadas aún por el compilador
  • MACROS Avanzados – Ahorro de memoria
  • MACROS y Funciones estándar
  • Parámetros de salida con MACROS

Pautas para desarrollar MACROS en C++

  • No se pueden dejar saltos de linea
  • No se recomienda el uso de comentarios
  • Las sentencias deben de terminar en “\” (Si la MACRO está escrita en más de una linea)

DEFINE de una variable

#define PINLED 13

void setup() {
  Serial.begin(9600);
  pinMode(PINLED,OUTPUT);
}

void loop() {
  digitalWrite(PINLED,HIGH);
  delay(1000);
  digitalWrite(PINLED,LOW);
  delay(1000);
}

 

DEFINE de una función

int _ItemNr=0;
#define ITEM_DUMMY() do { _ItemNr++; } while(0)

void setup() {
  Serial.begin(9600);
  Serial.println("Goodnight Moon");
  ITEM_DUMMY();
  ITEM_DUMMY();
}

void loop() {
  
  Serial.println(_ItemNr);
}

 

Operador ## – Nombres de variables siguiendo un patrón. Las variables no han sido creadas aún por el compilador

El operador ## se denomina operador concatenación en C++ y sirve para unir distintas palabras en el precompilador de manera que se pueden usar trucos para desarrollar estructuras en bucle.

int A;
#define P(no_) \
{ \
  for(A = 0 ; A <= no_ ; A++){\
     int PIN##A;\ 
     PIN##A = A;\
     Serial.println(PIN##A);\
  }\
}\
 
void setup() {
  Serial.begin(9600);
  P(0);
}
 
void loop() {
  P(12);
}

En este ejemplo se puede ver que se puede acceder a un elemento u objeto declarado dentro del Setup como es el Serial, desde una MACRO. Nos daría problemas si quisieramos acceder al monitor serie antes de establecer la velocidad de comunicación, por lo que deberemos de inicializar correctamente nuestro objeto.

También podemos utilizar bucles dentro de una MACRO con la sintaxis de C++ que es exactamente igual que la que ejecutaríamos dentro de una función estándar.

*En una Arduino UNO y  Esplora funciona debidamente, mientras que en una Arduino Mega no tiene ningún efecto. El operador ## lo identifica como parte del nombre de la variable. Funciona diferente con distintas placas.

*una cuestión interesante es que no se puede anidar el operador ## dentro del preprocesador. Por ejemplo, definir una MACRO en la que se añadan otros #define, con el objetivo de ocupar memoria variable.
#define MACRO(name) #include “name##foo”

Esto no se permite hacer, ya que estariamos llenando memoría desde a una función que produciría fallos en la compilación.

 

MACROS Avanzados – Ahorro de memoria

#include <HardwareSerial.h>

#define MYSERIAL Serial

#define  FORCE_INLINE __attribute__((always_inline)) inline
//Things to write to serial from Program memory. Saves 400 to 2k of RAM.
FORCE_INLINE void serialprintPGM(const char *str)
{
  char ch=pgm_read_byte(str);
  while(ch)
  {
    MYSERIAL.write(ch);
    ch=pgm_read_byte(++str);
  }
}

#define SERIAL_PROTOCOL(x) (MYSERIAL.print(x))
#define SERIAL_PROTOCOLLN(x) (MYSERIAL.print(x),MYSERIAL.write('\n'))

void setup() {
  MYSERIAL.begin(9600);
}

void loop() {
  SERIAL_PROTOCOL("Hello ");
  SERIAL_PROTOCOLLN("World");

}

MACROS y Funciones estándar

En este ejemplo, trataremos ahora de mezclar la declaración de Macros con funciones estándar, donde vamos a permitir acceder a una función con un parámetro de entrada que será el encargado de elegir la función declarada en concreto.



#define funcslow(type) \
{ \
  function_ ## type ## _slow (); \
}\

#define funcfast(type) \
{ \
  function_ ## type ## _fast (); \
}\

void function_walk_slow(){
  Serial.println("Walking Slowly");
}

void function_walk_fast(){
  Serial.println("Walking Fast");
}

void function_jump_slow(){
  Serial.println("Jumping Slowly");
}

void function_jump_fast(){
  Serial.println("Jumping Fast");
}

void setup() {
  Serial.begin(9600);
  funcslow(walk);
  funcslow(jump);
}
 
void loop() {
  funcfast(walk);
  funcfast(jump);
}

Parámetros de salida con MACROS


#define OUTPUTBOOL() (bool){false}
#define OUTPUTNUM(a,b) (float){a+b}
#define OUTPUTSTRING(data) (String){data}

void setup() {
  Serial.begin(9600);
}

void loop() {

  Serial.println(OUTPUTBOOL());
  Serial.println(OUTPUTNUM(1.5,5.2));
  Serial.println(OUTPUTSTRING("Hello World"));
  
}

En este caso concreto, hay que darse cuenta de que los parámetros de entrada no están entre comillas. Y por otra parte, si introducimos una palabra que no corresponde con ninguna función, el precompilador ya nos avisa de su inexistencia.

*En el siguiente Post escribo sobre un comportamiento equivalente en PHP.

 

Muchas veces puede aparecer este error, que suele aparecer cuando no se cumplen las condiciones de creación de MACROS

Invalid Preprocessing Token Error
lixo.c:42:1: error: pasting “.” and “red” does not give a valid preprocessing token